package com.zlcx.tz.live.service.impl;

import com.alibaba.fastjson.JSON;
import com.zlcx.tz.live.constant.RedisKeyConst;
import com.zlcx.tz.live.domain.LiveAgoraAcount;
import io.agora.recording.RecordingEventHandler;
import io.agora.recording.RecordingSDK;
import io.agora.recording.common.Common;
import io.agora.recording.common.Common.AUDIO_FORMAT_TYPE;
import io.agora.recording.common.Common.MIXED_AV_CODEC_TYPE;
import io.agora.recording.common.Common.REMOTE_VIDEO_STREAM_TYPE;
import io.agora.recording.common.Common.VIDEO_FORMAT_TYPE;
import io.agora.recording.common.RecordingConfig;
import io.agora.recording.common.RecordingEngineProperties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p></p>
 * <PRE>
 * <BR>    修改记录
 * <BR>-----------------------------------------------
 * <BR>    修改日期         修改人          修改内容
 * </PRE>
 *
 * @author zl
 * @version 1.0
 * @date Created in 2019/6/17 11:13
 * @copyright: Copyright (c) founders
 */
@Slf4j
@Service
public class RecordingService {


    @Value("${agora.agoraDir}")
    private String agoraDir;

    @Value("${agora.agoraRecordDir}")
    private String agoraRecordDir;

    @Value("${agora.isRecord}")
    private Boolean isRecord;

    private RecordingConfig configured;
    private RecordingSDK recordingSDKInstance;
    private Timer cleanTimer;

    @Resource(name="recordExecutor")
    private AsyncTaskExecutor recordExecutor;

    @PostConstruct
    public void init() {
        String applitePath = agoraDir+"/Agora_Recording_SDK_for_Linux_FULL/bin";
        String recordFileRootDir = agoraRecordDir;
        configured = new RecordingConfig();
        configured.channelProfile= Common.CHANNEL_PROFILE_TYPE.values()[1];
        configured.idleLimitSec=5 * 60;
        configured.isVideoOnly=true;
        configured.isAudioOnly=false;
        configured.isMixingEnabled=false;
        configured.mixResolution="360,640,15,500";
        configured.mixedVideoAudio= MIXED_AV_CODEC_TYPE.values()[MIXED_AV_CODEC_TYPE.MIXED_AV_DEFAULT.ordinal()];
        configured.appliteDir=applitePath;
        configured.recordFileRootDir=recordFileRootDir;
        configured.cfgFilePath="";
        configured.secret="";
        configured.decryptionMode="";
        configured.lowUdpPort=40000;
        configured.highUdpPort=41000;
        configured.captureInterval=2;
        configured.audioIndicationInterval=0;
        configured.decodeAudio=AUDIO_FORMAT_TYPE.values()[AUDIO_FORMAT_TYPE.AUDIO_FORMAT_DEFAULT_TYPE.ordinal()];
        configured.decodeVideo=VIDEO_FORMAT_TYPE.values()[VIDEO_FORMAT_TYPE.VIDEO_FORMAT_JPG_FILE_TYPE.ordinal()];
        configured.streamType=REMOTE_VIDEO_STREAM_TYPE.values()[REMOTE_VIDEO_STREAM_TYPE.REMOTE_VIDEO_STREAM_HIGH.ordinal()];
        configured.triggerMode=0;
        configured.proxyServer="";
        configured.audioProfile=0;
        configured.defaultVideoBgPath="";
        configured.defaultUserBgPath="";
        recordingSDKInstance = new RecordingSDK();
        cleanTimer = new Timer();


        if(isRecord){
            System.loadLibrary("recording");
        }

    }

    @Autowired
    private LiveAgoraAcountService agoraAcountService;

    @Autowired
    private RedisTemplate redisTemplate;

    public void startRecording(Long channelId) {
        if(!isRecord){
            return;
        }
        recordExecutor.execute(()->{
            try {
                LiveAgoraAcount agoraAcount = getCurrentUseAcount();
                if(agoraAcount==null){
                    log.error("[RecordingManager] getCurrentUseAcount null");
                    return;
                }

                RecordingOberserver oberserver = new RecordingOberserver(agoraAcount.getAppId(), channelId);
                oberserver.createChannel();
            }catch (Exception e){
                log.error("[RecordingManager] startRecording error,channelId={},e={}",channelId,e);
            }

        });
    }

//    public void startRecording(Long channelId) {
//
//        asyncTaskExecutor.execute(()->{
//            try {
//                LiveAgoraAcount agoraAcount = getCurrentUseAcount();
//                if(agoraAcount==null){
//                    log.error("[RecordingManager] getCurrentUseAcount null");
//                    return;
//                }
//
//                Process pcs = Runtime.getRuntime().exec("sh "+agoraDir+"/record.sh "+agoraAcount.getAppId()+" "+channelId);
//            }catch (Exception e){
//                log.error("[RecordingManager] startRecording error,channelId={},e={}",channelId,e);
//            }
//
//        });
//    }

    private LiveAgoraAcount getCurrentUseAcount() {
        return agoraAcountService.getCurrentUseAcountCache();
    }

    class RecordingOberserver implements RecordingEventHandler {

        private Map<String, UserInfo> audioChannels = new HashMap<>();
        private Map<String, UserInfo> videoChannels = new HashMap<>();
        public static final int BESTFIT_LAYOUT = 1;
        public static final int VERTICALPRESENTATION_LAYOUT = 2;
        private long mNativeHandle = 0;
        private Vector<Long> m_peers = new Vector<>();
        private int layoutMode = 0;
        private long maxResolutionUid = -1;
        private String storageDir = "./";
        private boolean m_receivingAudio = false;
        private boolean m_receivingVideo = false;

        private boolean isMixMode = false;
        private Common.CHANNEL_PROFILE_TYPE profile_type;
        private int width = 0;
        private int height = 0;
        private int fps = 0;
        private int kbps = 0;
        private String appId;
        private Long channelId;



        public RecordingOberserver(String appId, Long channelId) {
            this.appId=appId;
            this.channelId=channelId;
            recordingSDKInstance.registerOberserver(this);
            this.isMixMode = configured.isMixingEnabled;
            this.profile_type = configured.channelProfile;
            if (this.isMixMode && !configured.isAudioOnly) {
                String[] sourceStrArray = configured.mixResolution.split(",");
                if (sourceStrArray.length != 4) {
                    log.error("[RecordingOberserver] Illegal resolution:{}",configured.mixResolution);
                    return;
                }
                this.width = Integer.valueOf(sourceStrArray[0]).intValue();
                this.height = Integer.valueOf(sourceStrArray[1]).intValue();
                this.fps = Integer.valueOf(sourceStrArray[2]).intValue();
                this.kbps = Integer.valueOf(sourceStrArray[3]).intValue();
            }

        }

        @Override
        public void nativeObjectRef(long nativeHandle) {
            this.mNativeHandle = nativeHandle;
        }

        @Override
        public void onLeaveChannel(int reason) {
            log.info("[RecordingOberserver] onLeaveChannel,code:{}",reason);
        }

        @Override
        public void onError(int error, int stat_code) {
            log.error("[RecordingOberserver] onError,error:{},stat code:{}",error,stat_code);
        }

        @Override
        public void onWarning(int warn) {
            log.warn("[RecordingOberserver] onWarning,warn:{}",warn);
        }

        @Override
        public void onJoinChannelSuccess(String channelId, long uid) {
            if (configured.decodeAudio != AUDIO_FORMAT_TYPE.AUDIO_FORMAT_DEFAULT_TYPE) {
                cleanTimer.schedule(new RecordingCleanTimer(this), 10000);
            }
            log.info("[RecordingOberserver] joinChannel success, channelId:{}, uid:{}",channelId, uid);
        }

        @Override
        public void onUserOffline(long uid, int reason) {
            log.info("[RecordingOberserver] onUserOffline uid:{},offline reason:{}",uid,reason);
            m_peers.remove(uid);
            printUsersInfo(m_peers);
            setVideoMixingLayout();
        }

        @Override
        public void onUserJoined(long uid, String recordingDir) {
            log.info("[RecordingOberserver] onUserJoined uid:{},recordingDir:{}",uid,recordingDir);
            storageDir = recordingDir;
            m_peers.add(uid);
            printUsersInfo(m_peers);
            setVideoMixingLayout();
            String recordingPicDir = RedisKeyConst.getRecordingPicDirKey(uid,channelId);
            redisTemplate.opsForValue().set(recordingPicDir,recordingDir,2, TimeUnit.DAYS);
        }

        @Override
        public void onActiveSpeaker(long uid) {
            log.info("[RecordingOberserver] User:{} is speaking",uid);
        }

        @Override
        public void audioFrameReceived(long uid, Common.AudioFrame frame) {
            byte[] buf = null;
            long size = 0;
            checkUser(uid, true);
            if (frame.type == Common.AUDIO_FRAME_TYPE.AUDIO_FRAME_RAW_PCM) {// pcm
                buf = frame.pcm.pcmBuf;
                size = frame.pcm.pcmBufSize;
            } else {// aac
                buf = frame.aac.aacBuf;
                size = frame.aac.aacBufSize;
            }
            writeBytesToFileClassic(uid, buf, size, true);
        }

        @Override
        public void videoFrameReceived(long uid, int type, Common.VideoFrame frame, int rotation) {

            byte[] buf = null;
            long size = 0;
            checkUser(uid, false);
            if (type == 0) {// yuv
                buf = frame.yuv.buf;
                size = frame.yuv.bufSize;
                if (buf == null) {
                    log.warn("[RecordingOberserver] videoFrameReceived null");
                }
            } else if (type == 1) {// h264
                buf = frame.h264.buf;
                size = frame.h264.bufSize;
            } else if (type == 2) { // jpg
                String path = storageDir + Long.toString(uid) + System.currentTimeMillis() + ".jpg";
                buf = frame.jpg.buf;
                size = frame.jpg.bufSize;
                try {
                    FileOutputStream channel = new FileOutputStream(path, true);
                    channel.write(buf, 0, (int) size);
                    channel.close();
                } catch (Exception e) {
                    log.error("[RecordingOberserver] videoFrameReceived Error write to {}",path);
                }
                return;
            }
            writeBytesToFileClassic(uid, buf, size, false);
        }

        @Override
        public void stopCallBack() {
            log.info("[RecordingOberserver] receive stop from JNI ");
        }

        @Override
        public void recordingPathCallBack(String path) {
            storageDir = path;
        }

        @Override
        public void onAudioVolumeIndication(Common.AudioVolumeInfo[] infos) {
            if (infos.length == 0){
                return;
            }

            for (int i = 0; i < infos.length; i++) {
                log.info("[RecordingOberserver] onAudioVolumeIndication User:{}, audio volume:{}", infos[i].uid , infos[i].volume);
            }
        }

        @Override
        public void onFirstRemoteVideoDecoded(long uid, int width, int height, int elapsed) {
            log.info("[RecordingOberserver] onFirstRemoteVideoDecoded User:{}, width:{}, height:{}, elapsed:{}",uid,width, height,elapsed);
        }

        @Override
        public void onFirstRemoteAudioFrame(long uid, int elapsed) {
            log.info("[RecordingOberserver] onFirstRemoteAudioFrame User:{}, elapsed:{}",uid,elapsed);
        }

        @Override
        public void onReceivingStreamStatusChanged(boolean receivingAudio, boolean receivingVideo) {
            log.info("[RecordingOberserver] onReceivingStreamStatusChanged pre receiving audio status is {}, now receiving audio status is {}", m_receivingAudio ,receivingAudio);
            log.info("[RecordingOberserver] onReceivingStreamStatusChanged pre receiving video status is {}, now receiving audio status is {}",m_receivingVideo,receivingVideo);
            m_receivingAudio = receivingAudio;
            m_receivingVideo = receivingVideo;
        }

        @Override
        public void onConnectionLost() {
            log.info("[RecordingOberserver] connection is lost");
        }

        @Override
        public void onConnectionInterrupted() {
            log.info("[RecordingOberserver] connection is interrupted");
        }

        public void createChannel() {
            log.info("[RecordingOberserver] createChannel,appId:{},channelId:{}",appId,channelId);
            log.debug("[RecordingOberserver] createChannel configured={}",JSON.toJSONString(configured));
            String channelKey = "";
            int uid = 0;
            int logLevel = 5;
            boolean create = recordingSDKInstance.createChannel(appId, channelKey, channelId.toString(), uid, configured, logLevel);
            log.info("[RecordingOberserver] createChanneled,appId:{},channelId:{},create:{}",appId,channelId,create);
            unRegister();
            log.info("[RecordingOberserver] jni layer has been exited...,appId:{},channelId:{}",appId,channelId);
        }
        public void unRegister() {
            recordingSDKInstance.unRegisterOberserver(this);
        }

        public boolean leaveChannel(long nativeHandle) {
            return recordingSDKInstance.leaveChannel(nativeHandle);
        }

        public int startService(long nativeHandle) {
            return recordingSDKInstance.startService(nativeHandle);
        }

        public int stopService(long nativeHandle) {
            return recordingSDKInstance.stopService(nativeHandle);
        }

        public RecordingEngineProperties getProperties(long nativeHandle) {
            return recordingSDKInstance.getProperties(nativeHandle);
        }

        private void clean() {
            synchronized (this) {
                long now = System.currentTimeMillis();

                Iterator<Map.Entry<String, UserInfo>> audio_it = audioChannels.entrySet().iterator();
                while (audio_it.hasNext()) {
                    Map.Entry<String, UserInfo> entry = audio_it.next();
                    UserInfo info = entry.getValue();
                    if (now - info.getLast_receive_time() > 3000) {
                        try {
                            info.getChannel().close();
                        } catch (IOException e) {
                            log.error("[RecordingOberserver] audio Channel close error,UserInfo={},e={}", JSON.toJSONString(info), e);
                        }
                        audio_it.remove();
                    }
                }
                Iterator<Map.Entry<String, UserInfo>> video_it = videoChannels.entrySet().iterator();
                while (video_it.hasNext()) {
                    Map.Entry<String, UserInfo> entry = video_it.next();
                    UserInfo info = entry.getValue();
                    if (now - info.getLast_receive_time() > 3000) {
                        try {
                            info.getChannel().close();
                        } catch (IOException e) {
                            log.error("[RecordingOberserver] video Channel close error,UserInfo={},e={}", JSON.toJSONString(info), e);
                        }
                        video_it.remove();
                    }
                }
            }
            cleanTimer.schedule(new RecordingCleanTimer(this), 10000);
        }

        private void printUsersInfo(Vector vector) {
            int size = vector.size();
            for (Long l : m_peers) {
                log.debug("[RecordingOberserver] printUsersInfo user size:{},user:{}",size,l);
            }
        }

        private int setVideoMixingLayout() {
            int mPeersSize = m_peers.size();
            Common ei = new Common();
            Common.VideoMixingLayout layout = ei.new VideoMixingLayout();
            int max_peers = profile_type == Common.CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_COMMUNICATION ? 7 : 17;
            if (mPeersSize > max_peers) {
                log.info("[RecordingOberserver] peers size is bigger than max m_peers:{}",mPeersSize);
                return -1;
            }

            if (!isMixMode()){
                return -1;
            }

            layout.canvasHeight = height;
            layout.canvasWidth = width;
            layout.backgroundColor = "#23b9dc";
            layout.regionCount = mPeersSize;

            if (!m_peers.isEmpty()) {
                log.info("[RecordingOberserver] setVideoMixingLayout m_peers is not empty, start layout");
                Common.VideoMixingLayout.Region[] regionList = new Common.VideoMixingLayout.Region[m_peers.size()];
                log.info("[RecordingOberserver] mixing layout mode:{}",layoutMode);
                if (layoutMode == BESTFIT_LAYOUT) {
                    adjustBestFitVideoLayout(regionList, layout);
                } else if (layoutMode == VERTICALPRESENTATION_LAYOUT) {
                    adjustVerticalPresentationLayout(maxResolutionUid, regionList, layout);
                } else {
                    adjustDefaultVideoLayout(regionList, layout);
                }

                layout.regions = regionList;

            } else {
                layout.regions = null;
            }
            return recordingSDKInstance.setVideoMixingLayout(mNativeHandle, layout);
        }

        private boolean isMixMode() {
            return isMixMode;
        }

        private void adjustBestFitVideoLayout(Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            if (m_peers.size() == 1) {
                adjustBestFitLayout_Square(regionList, 1, layout);
            } else if (m_peers.size() == 2) {
                adjustBestFitLayout_2(regionList, layout);
            } else if (2 < m_peers.size() && m_peers.size() <= 4) {
                adjustBestFitLayout_Square(regionList, 2, layout);
            } else if (5 <= m_peers.size() && m_peers.size() <= 9) {
                adjustBestFitLayout_Square(regionList, 3, layout);
            } else if (10 <= m_peers.size() && m_peers.size() <= 16) {
                adjustBestFitLayout_Square(regionList, 4, layout);
            } else if (m_peers.size() == 17) {
                adjustBestFitLayout_17(regionList, layout);
            } else {
                log.warn("[RecordingOberserver] adjustBestFitVideoLayout is more than 17 users");
            }
        }

        private void adjustVerticalPresentationLayout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            log.info("[RecordingOberserver] begin adjust vertical presentation layout,peers size:{}, maxResolutionUid:{}",m_peers.size(), maxResolutionUid);
            if (m_peers.size() <= 5) {
                adjustVideo5Layout(maxResolutionUid, regionList, layout);
            } else if (m_peers.size() <= 7) {
                adjustVideo7Layout(maxResolutionUid, regionList, layout);
            } else if (m_peers.size() <= 9) {
                adjustVideo9Layout(maxResolutionUid, regionList, layout);
            } else {
                adjustVideo17Layout(maxResolutionUid, regionList, layout);
            }
        }

        private void adjustDefaultVideoLayout(Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            regionList[0] = layout.new Region();
            regionList[0].uid = m_peers.get(0);
            regionList[0].x = 0.f;
            regionList[0].y = 0.f;
            regionList[0].width = 1.f;
            regionList[0].height = 1.f;
            regionList[0].alpha = 1.f;
            regionList[0].renderMode = 0;
            float f_width = width;
            float f_height = height;
            float canvasWidth = f_width;
            float canvasHeight = f_height;
            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            for (int i = 1; i < m_peers.size(); i++) {
                regionList[i] = layout.new Region();

                regionList[i].uid = m_peers.get(i);
                float f_x = (i - 1) % 4;
                float f_y = (i - 1) / 4;
                float xIndex = f_x;
                float yIndex = f_y;
                regionList[i].x = xIndex * (viewWidth + viewHEdge) + viewHEdge;
                regionList[i].y = 1 - (yIndex + 1) * (viewHeight + viewVEdge);
                regionList[i].width = viewWidth;
                regionList[i].height = viewHeight;
                regionList[i].alpha = (i + 1);
                regionList[i].renderMode = 0;
            }
            layout.regions = regionList;
        }

        private void adjustBestFitLayout_Square(Common.VideoMixingLayout.Region[] regionList, int nSquare, Common.VideoMixingLayout layout) {
            float canvasWidth = (float) width;
            float canvasHeight = (float) height;
            float viewWidth = (float) (1.f * 1.0 / nSquare);
            float viewHEdge = (float) (1.f * 1.0 / nSquare);
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int peersCount = m_peers.size();
            for (int i = 0; i < peersCount; i++) {
                regionList[i] = layout.new Region();
                float xIndex = (float) (i % nSquare);
                float yIndex = (float) (i / nSquare);
                regionList[i].uid = m_peers.get(i);
                regionList[i].x = 1.f * 1.0 / nSquare * xIndex;
                regionList[i].y = 1.f * 1.0 / nSquare * yIndex;
                regionList[i].width = viewWidth;
                regionList[i].height = viewHEdge;
                regionList[i].alpha = (double) (i + 1);
                regionList[i].renderMode = 0;
            }
        }

        private void adjustBestFitLayout_2(Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            float canvasWidth = (float) width;
            float canvasHeight = (float) height;
            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int peersCount = m_peers.size();
            for (int i = 0; i < peersCount; i++) {
                regionList[i] = layout.new Region();
                regionList[i].uid = m_peers.get(i);
                regionList[i].x = (((i + 1) % 2) == 0) ? 0 : 0.5;
                regionList[i].y = 0.f;
                regionList[i].width = 0.5f;
                regionList[i].height = 1.f;
                regionList[i].alpha = i + 1;
                regionList[i].renderMode = 0;
            }
        }

        private void adjustBestFitLayout_17(Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            float canvasWidth = (float) width;
            float canvasHeight = (float) height;
            int n = 5;
            float viewWidth = (float) (1.f * 1.0 / n);
            float viewHEdge = (float) (1.f * 1.0 / n);
            float totalWidth = (float) (1.f - viewWidth);
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int peersCount = m_peers.size();
            for (int i = 0; i < peersCount; i++) {
                regionList[i] = layout.new Region();
                float xIndex = (float) (i % (n - 1));
                float yIndex = (float) (i / (n - 1));
                regionList[i].uid = m_peers.get(i);
                regionList[i].width = viewWidth;
                regionList[i].height = viewHEdge;
                regionList[i].alpha = i + 1;
                regionList[i].renderMode = 0;
                if (i == 16) {
                    regionList[i].x = (1 - viewWidth) * (1.f / 2) * 1.f;
                    log.info("[RecordingOberserver] special layout for 17 x is:{}",regionList[i].x);
                } else {
                    regionList[i].x = 0.5f * viewWidth + viewWidth * xIndex;
                }
                regionList[i].y = (1.0 / n) * yIndex;
            }
        }

        private void adjustVideo5Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            boolean flag = false;

            float canvasWidth = (float) width;
            float canvasHeight = (float) height;

            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int number = 0;

            int i = 0;
            for (; i < m_peers.size(); i++) {
                regionList[i] = layout.new Region();
                if (maxResolutionUid == m_peers.get(i)) {
                    log.info("[RecordingOberserver] adjustVideo5Layout equal with configured user uid:{}",maxResolutionUid);
                    flag = true;
                    setMaxResolutionUid(number, maxResolutionUid, regionList, 0.8);
                    number++;
                    continue;
                }
                regionList[number].uid = m_peers.get(i);
                //float xIndex = ;
                float yIndex = flag ? ((float) (number - 1 % 4)) : ((float) (number % 4));
                regionList[number].x = 1.f * 0.8;
                regionList[number].y = (0.25) * yIndex;
                regionList[number].width = 1.f * (1 - 0.8);
                regionList[number].height = 1.f * (0.25);
                regionList[number].alpha = (double) number;
                regionList[number].renderMode = 0;
                number++;
                if (i == 4 && !flag) {
                    changeToVideo7Layout(maxResolutionUid, regionList, layout);
                }
            }
        }

        private void changeToVideo7Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            log.info("[RecordingOberserver] changeToVideo7Layout");
            adjustVideo7Layout(maxResolutionUid, regionList, layout);
        }

        private void adjustVideo7Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            boolean flag = false;
            float canvasWidth = (float) width;
            float canvasHeight = (float) height;

            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int number = 0;

            int i = 0;
            for (; i < m_peers.size(); i++) {
                regionList[i] = layout.new Region();
                if (maxResolutionUid == m_peers.get(i)) {
                    log.info("[RecordingOberserver] adjustVideo7Layout equal with configured user uid:{}",maxResolutionUid);
                    flag = true;
                    setMaxResolutionUid(number, maxResolutionUid, regionList, 6.f / 7);
                    number++;
                    continue;
                }
                regionList[number].uid = m_peers.get(i);
                float yIndex = flag ? ((float) number - 1 % 6) : ((float) (number % 6));
                regionList[number].x = 6.f / 7;
                regionList[number].y = (1.f / 6) * yIndex;
                regionList[number].width = (1.f / 7);
                regionList[number].height = (1.f / 6);
                regionList[number].alpha = (double) number;
                regionList[number].renderMode = 0;
                number++;
                if (i == 6 && !flag) {
                    changeToVideo9Layout(maxResolutionUid, regionList, layout);
                }
            }

        }

        private void changeToVideo9Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            log.info("[RecordingOberserver] changeToVideo9Layout");
            adjustVideo9Layout(maxResolutionUid, regionList, layout);
        }

        private void adjustVideo9Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            boolean flag = false;

            float canvasWidth = (float) width;
            float canvasHeight = (float) height;

            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int number = 0;

            int i = 0;
            for (; i < m_peers.size(); i++) {
                regionList[i] = layout.new Region();
                if (maxResolutionUid == m_peers.get(i)) {
                    log.info("[RecordingOberserver] adjustVideo9Layout equal with configured user uid:{}", maxResolutionUid);
                    flag = true;
                    setMaxResolutionUid(number, maxResolutionUid, regionList, 9.f / 5);
                    number++;
                    continue;
                }
                regionList[number].uid = m_peers.get(i);
                float yIndex = flag ? ((float) (number - 1 % 8)) : ((float) (number % 8));
                regionList[number].x = 8.f / 9;
                regionList[number].y = (1.f / 8) * yIndex;
                regionList[number].width = 1.f / 9;
                regionList[number].height = 1.f / 8;
                regionList[number].alpha = (double) number;
                regionList[number].renderMode = 0;
                number++;
                if (i == 8 && !flag) {
                    changeToVideo17Layout(maxResolutionUid, regionList, layout);
                }
            }
        }

        private void changeToVideo17Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            log.info("[RecordingOberserver] changeToVideo17Layout");
            adjustVideo17Layout(maxResolutionUid, regionList, layout);
        }

        private void adjustVideo17Layout(long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, Common.VideoMixingLayout layout) {
            boolean flag = false;
            float canvasWidth = (float) width;
            float canvasHeight = (float) height;

            float viewWidth = 0.235f;
            float viewHEdge = 0.012f;
            float viewHeight = viewWidth * (canvasWidth / canvasHeight);
            float viewVEdge = viewHEdge * (canvasWidth / canvasHeight);
            int number = 0;
            log.info("[RecordingOberserver] adjustVideo17Layoutenter m_peers size is:{}, maxResolutionUid:{}" , m_peers.size(),maxResolutionUid);
            for (int i = 0; i < m_peers.size(); i++) {
                regionList[i] = layout.new Region();
                if (maxResolutionUid == m_peers.get(i)) {
                    flag = true;
                    setMaxResolutionUid(number, maxResolutionUid, regionList, 0.8);
                    number++;
                    continue;
                }
                if (!flag && i == 16) {
                    log.warn("[RecordingOberserver] Not the configured uid, and small regions is sixteen, so ignore this user:{}",m_peers.get(i));
                    break;
                }

                regionList[number].uid = m_peers.get(i);
                //float xIndex = 0.833f;
                float yIndex = flag ? ((float) ((number - 1) % 8)) : ((float) (number % 8));
                regionList[number].x = ((flag && i > 8) || (!flag && i >= 8)) ? (9.f / 10) : (8.f / 10);
                regionList[number].y = (1.f / 8) * yIndex;
                regionList[number].width = 1.f / 10;
                regionList[number].height = 1.f / 8;
                regionList[number].alpha = (double) number;
                regionList[number].renderMode = 0;
                number++;
            }
        }

        private void setMaxResolutionUid(int number, long maxResolutionUid, Common.VideoMixingLayout.Region[] regionList, double weight_ratio) {
            regionList[number].uid = maxResolutionUid;
            regionList[number].x = 0.f;
            regionList[number].y = 0.f;
            regionList[number].width = 1.f * weight_ratio;
            regionList[number].height = 1.f;
            regionList[number].alpha = 1.f;
            regionList[number].renderMode = 1;
        }

        private void checkUser(long uid, boolean isAudio) {
            String path = storageDir + Long.toString(uid);
            String key = Long.toString(uid);
            synchronized (this) {
                if (isAudio && !audioChannels.containsKey(key)) {
                    if (configured.decodeAudio == AUDIO_FORMAT_TYPE.AUDIO_FORMAT_AAC_FRAME_TYPE ||
                            configured.decodeAudio == AUDIO_FORMAT_TYPE.AUDIO_FORMAT_PCM_FRAME_TYPE ||
                            configured.decodeAudio == AUDIO_FORMAT_TYPE.AUDIO_FORMAT_MIXED_PCM_FRAME_TYPE) {
                        String audioPath;
                        if (configured.decodeAudio == AUDIO_FORMAT_TYPE.AUDIO_FORMAT_AAC_FRAME_TYPE) {
                            audioPath = path + ".aac";
                        } else {
                            audioPath = path + ".pcm";
                        }
                        try {
                            UserInfo info = new UserInfo();
                            info.channel = new FileOutputStream(audioPath, true);
                            info.last_receive_time = System.currentTimeMillis();
                            audioChannels.put(key, info);
                        } catch (FileNotFoundException e) {
                            log.error("[RecordingOberserver] Can't find file : {}",audioPath);
                        }
                    }
                }

                if (!isAudio && !videoChannels.containsKey(key)) {
                    if (configured.decodeVideo == VIDEO_FORMAT_TYPE.VIDEO_FORMAT_YUV_FRAME_TYPE ||
                            configured.decodeVideo == VIDEO_FORMAT_TYPE.VIDEO_FORMAT_H264_FRAME_TYPE) {
                        String videoPath;
                        if (configured.decodeVideo == VIDEO_FORMAT_TYPE.VIDEO_FORMAT_H264_FRAME_TYPE) {
                            videoPath = path + ".h264";
                        } else {
                            videoPath = path + ".yuv";
                        }
                        try {
                            UserInfo info = new UserInfo();
                            info.channel = new FileOutputStream(videoPath, true);
                            info.last_receive_time = System.currentTimeMillis();
                            videoChannels.put(key, info);
                        } catch (FileNotFoundException e) {
                            log.error("[RecordingOberserver] Can't find file : {}",videoPath);
                        }
                    }
                }
            }
        }


        private void writeBytesToFileClassic(long uid, byte[] byteBuffer, long size, boolean isAudio) {
            if (byteBuffer == null) {
                log.error("[RecordingOberserver] WriteBytesToFileClassic but byte buffer is null!");
                return;
            }

            synchronized (this) {
                try {
                    UserInfo info = isAudio ? audioChannels.get(Long.toString(uid)) : videoChannels.get(Long.toString(uid));
                    if (info != null) {
                        info.channel.write(byteBuffer, 0, (int) size);
                        info.channel.flush();
                        info.last_receive_time = System.currentTimeMillis();
                    } else {
                        log.error("[RecordingOberserver] writeBytesToFileClassic Channel is null");
                    }
                } catch (IOException e) {
                    log.error("[RecordingOberserver] writeBytesToFileClassic error,e={}",e);
                }
            }
        }


    }

    @Data
    class UserInfo {
        private long uid;
        private long last_receive_time;
        private FileOutputStream channel;
    }

    class RecordingCleanTimer extends TimerTask {
        RecordingOberserver oberserver;

        public RecordingCleanTimer(RecordingOberserver oberserver) {
            this.oberserver = oberserver;
        }

        @Override
        public void run() {
            oberserver.clean();
        }
    }

}




